package com.fjeld.server.worker;

import static com.fjeld.middleware.jaxb.V1Message.CorrespondenceStatus;
import static com.fjeld.middleware.jaxb.V1Message.CorrespondenceType;
import static com.fjeld.middleware.jaxb.V1User.UserStatus;

import com.fjeld.middleware.constants.PropertiesManager;
import com.fjeld.middleware.jaxb.V1Message;
import com.fjeld.middleware.jaxb.V1User;
import com.fjeld.middleware.jaxb.util.V1MessagePool;
import com.fjeld.server.cache.Cache;
import com.fjeld.server.cache.impl.UsersCache;
import org.apache.log4j.Logger;

import javax.security.auth.login.LoginContext;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Created by IntelliJ IDEA.
 * User: fjeld
 * Date: 02/01/2012
 * Time: 23:07
 * <p/>
 * Accepts a work item and works it, producing an outgoing response.
 */
public class WorkerThread implements Runnable
{
    private static final Logger LOGGER = Logger.getLogger(WorkerThread.class);
    private BlockingQueue<V1Message> workQueue, publishQueue;
    private V1MessagePool v1MessagePool = new V1MessagePool();
    private Cache<V1User> usersCache = UsersCache.getInstance();
    private int threadID;

    public WorkerThread(int threadID, BlockingQueue<V1Message> publishQueue)
    {
        LOGGER.info("WT-" + threadID + ": Creating...");
        this.threadID = threadID;
        this.publishQueue = publishQueue;

        String queueSizeString = PropertiesManager.getProperties().getProperty("WorkerThread.QueueSize", "1000");
        Integer queueSize = Integer.parseInt(queueSizeString);
        workQueue = new LinkedBlockingQueue<V1Message>(queueSize);

        LOGGER.info("WT-" + threadID + ": Size of incoming work queue: " + queueSize);
        LOGGER.info("WT-" + threadID + ": Finished creation.");
    }

    public void run()
    {
        while (true)
        {
            try
            {
                // Wait for and fetch the object to be worked on by using the new take()
                LOGGER.info("WT-" + threadID + ": Waiting for work...");
                V1Message v1Message = workQueue.take();

                V1Message processing = v1MessagePool.getV1Message();
                processing.setCorrespondenceType(v1Message.getCorrespondenceType());
                processing.setCorrespondenceStatus(CorrespondenceStatus.Processing.toString());
                processing.setCorrespondenceID(v1Message.getCorrespondenceID());
                processing.setSessionID(v1Message.getSessionID());
                processing.setCreateTime(System.currentTimeMillis());
                publishQueue.put(processing);

                // Work the item.
                LOGGER.info("WT-" + threadID + ": Processing work item.");

                // Prepare response object
                V1Message response;

                String correspondenceTypeString = v1Message.getCorrespondenceType();
                switch (CorrespondenceType.valueOf(correspondenceTypeString))
                {
                    case Login:
                        response = processLogin(v1Message);
                        break;
                    case Logout:
                        response = processLogout(v1Message);
                        break;
                    case CreateUser:
                        response = processCreateUser(v1Message);
                        break;
                    default:
                        response = reject(v1Message, "Unknown request type: " + correspondenceTypeString);
                }

                // Send the response.
                LOGGER.info("WT-" + threadID + ": Sending response.");
                publishQueue.put(response);
                LOGGER.info("WT-" + threadID + ": Finished.");
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    private V1Message createResponseSkeleton(V1Message incoming)
    {
        V1Message response = v1MessagePool.getV1Message();
        response.setCorrespondenceType(incoming.getCorrespondenceType());
        response.setCorrespondenceID(incoming.getCorrespondenceID());
        response.setUserID(incoming.getUserID());
        response.setSessionID(incoming.getSessionID());
        response.setCreateTime(System.currentTimeMillis());
        return response;
    }

    private V1Message reject(V1Message incoming, String rejectText)
    {
        V1Message response = createResponseSkeleton(incoming);
        response.setCorrespondenceStatus(CorrespondenceStatus.Rejected.toString());
        response.setText(rejectText);
        return response;
    }

    private V1Message login(V1Message incoming, String text)
    {
        V1Message response = createResponseSkeleton(incoming);
        response.setCorrespondenceStatus(CorrespondenceStatus.Accepted.toString());
        response.setSessionID(incoming.getSessionID());
        response.setText("Logged in." + text);
        return response;
    }

    private V1Message processLogin(V1Message incoming)
    {
        V1Message response = null;
        String UUID = incoming.getUserID();
        V1User v1User = usersCache.get(UUID);

        if (null != v1User)
        {
            UserStatus status = UserStatus.valueOf(v1User.getStatus());
            switch (status)
            {
                case Deactivated:
                    LOGGER.info("Login reject: deactivated");
                    response = reject(incoming, "User has been deactivated.");
                    break;
                case Banned:
                    LOGGER.info("Login reject: banned");
                    response = reject(incoming, "User is banned.");
                    break;
                case LoggedIn:
                    LOGGER.info("Login accepted: logged in already");
                    response = login(incoming, " User was already logged in. New session ID mapped.");
                    break;
                case LoggedOut:
                    LOGGER.info("Login accepted: was loggedout");
                    response = login(incoming, "");
                    break;
                case Active:
                    LOGGER.info("Login accepted: user was active");
                    response = login(incoming, "");
                    break;
            }

            v1User.setSessionID(incoming.getSessionID());
            v1User.setLastLoginTimeInMillis(incoming.getCreateTime());
            v1User.setLastInteractionTimeInMillis(incoming.getCreateTime());
            v1User.setLastInteractionType(incoming.getCorrespondenceType());
            v1User.setStatus(response.getCorrespondenceStatus());
        } else
        {
            LOGGER.info("User does not exist.");
            response = reject(incoming, "User does not exist.");
        }

        return response;
    }

    private V1Message processCreateUser(V1Message incoming)
    {
        V1Message response = null;
        String UUID = incoming.getUserID();
        V1User v1User = usersCache.get(UUID);

        if (null == v1User)
        {
            v1User = new V1User(incoming);
            usersCache.save(v1User.getUUID(), v1User);

            response = createResponseSkeleton(incoming);
            response.setCorrespondenceStatus(CorrespondenceStatus.Accepted.toString());
            response.setSessionID(incoming.getSessionID());
            response.setText("User created.");

            v1User.setSessionID(incoming.getSessionID());
            v1User.setLastLoginTimeInMillis(incoming.getCreateTime());
            v1User.setLastInteractionTimeInMillis(incoming.getCreateTime());
            v1User.setLastInteractionType(incoming.getCorrespondenceType());
        } else
        {
            response = reject(incoming, "User already exists. Choose another username.");
        }

        return response;
    }

    private V1Message processLogout(V1Message incoming)
    {
        V1Message response = null;
        String UUID = incoming.getUserID();
        V1User v1User = usersCache.get(UUID);

        if (null != v1User)
        {
            UserStatus status = UserStatus.valueOf(v1User.getStatus());
            switch (status)
            {
                case Deactivated:
                    response = reject(incoming, "User has been deactivated.");
                    break;
                case Banned:
                    response = reject(incoming, "User is banned.");
                    break;
                default:
                    response = createResponseSkeleton(incoming);
                    response.setCorrespondenceStatus(CorrespondenceStatus.Accepted.toString());
                    response.setSessionID(incoming.getSessionID());
                    response.setText("Logged out.");
                    // User is now logged out. Has no session ID associated with it now.
                    v1User.setSessionID("");
                    break;
            }

            v1User.setLastInteractionTimeInMillis(incoming.getCreateTime());
            v1User.setLastInteractionType(incoming.getCorrespondenceType());
            v1User.setStatus(response.getCorrespondenceStatus());
        } else
        {
            response = reject(incoming, "User does not exist.");
        }

        return response;
    }

    public BlockingQueue getWorkQueue()
    {
        return workQueue;
    }
}
